using System;
using System.Security;
using System.Security.Principal;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Threading;
using InfiniTec.Security.Privileges;

namespace InfiniTec.Security
{
    /// <summary>
    /// Encapsulates the impersonation process.
    /// </summary>
    /// <example>
    /// using (ImpersonationScope Scope = new ImpersonationScope("username", password, LogonType.Interactive)) {
    ///     // do something here
    /// }
    /// </example>
    public class ImpersonationScope: IDisposable
    {
        private LogonType _LogonType;
        private WindowsImpersonationContext _Scope;
        private CredentialEntry _CredentialEntry;

        private PrivilegeCollection _Privileges;

        /// <summary>
        /// Returns the <see cref="Privileges"/> held by the current token.
        /// </summary>
        public PrivilegeCollection Privileges
        {
            get {
                if (_CredentialEntry == null) throw new InvalidOperationException("Invalid credential entry");
                if (_Privileges == null) _Privileges = new PrivilegeCollection(_CredentialEntry.Identity);
                return _Privileges; 
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="CredentialEntry"/> associated with the current instance.
        /// </summary>
        protected CredentialEntry CredentialEntry
        {
            get { return _CredentialEntry; }
            set { _CredentialEntry = value; }
        }
	
        /// <summary>
        /// Creates a new instance of this class with the specified credentials and <see cref="LogonType"/>.
        /// </summary>
        /// <param name="username">The Username to use.</param>
        /// <param name="password">The password to use.</param>
        /// <param name="logonType">The <see cref="LogonType"/> to use.</param>
        public ImpersonationScope(string username, SecureString password, LogonType logonType):
            this (username, password, logonType, true) {}

        /// <summary>
        /// Creates a new instance of this class with the specified credentials and <see cref="LogonType"/>.
        /// </summary>
        /// <param name="username">The Username to use.</param>
        /// <param name="password">The password to use.</param>
        /// <param name="logonType">The <see cref="LogonType"/> to use.</param>
        /// <param name="impersonateImmediately">If true, the impersonation is performed immediately.</param>
        public ImpersonationScope(string username, SecureString password, LogonType logonType, bool impersonateImmediately)
        {
            _CredentialEntry = new CredentialEntry(username, password, logonType);

            if (impersonateImmediately) Impersonate();
        }

        /// <summary>
        /// This contructor can be used by developers who want to extend this class.
        /// </summary>
        protected ImpersonationScope() { }

        /// <summary>
        /// Gets or sets the <see cref="LogonType" /> used for impersonation.
        /// </summary>
        public LogonType LogonType
        {
            get { return _LogonType; }
            set { _LogonType = value; }
        }

        /// <summary>
        /// Returns the username of the current impersonation Scope
        /// </summary>
        public virtual string Username
        {
            get
            {
                
                return _CredentialEntry.Username;
            }
        }
        /// <summary>
        /// Returns the password of the current impersonation Scope
        /// </summary>
        public virtual SecureString Password
        {
            get
            {
                return _CredentialEntry.Password;
            }
        }

        /// <summary>
        /// Returns true, if the current thread is in impersonation mode. False, otherwise.
        /// </summary>
        public bool Impersonated
        {
            get { return _Scope != null; }
        }

        /// <summary>
        /// Impersonates as another user. Calls the specified <see cref="AcquireCredentialEventArgs"/>
        /// method to get a valid username/password combination, if no credential set can be found
        /// in the <see cref="CredentialManager"/> for the given target name.
        /// </summary>
        public virtual void Impersonate()
        {
            if (Impersonated) throw new InvalidOperationException("Already impersonated!");

            try
            {
                _Scope = _CredentialEntry.Identity.Impersonate();
            }
            catch (COMException ex)
            {
                if (ex.ErrorCode != -2147023570) throw;

                throw new InvalidCredentialException("The given username/password combination was invalid.", ex);
            }
        }

        /// <summary>
        /// Starts the given process under the current impersonation Scope.
        /// </summary>
        /// <param name="info">The process info to use</param>
        public virtual Process StartProcess(ProcessStartInfo info)
        {
            Process result;

            if (info == null) throw new ArgumentNullException("info", "The info parameter must not be null");
            if (Impersonated) throw new InvalidOperationException("Cannot start a process with different credentials when impersonated.");
            result = null;

            try
            {
                info.UserName = _CredentialEntry.Username;
                info.Domain = _CredentialEntry.Domain;
                info.Password = _CredentialEntry.Password;
                result = Process.Start(info);
            }
            catch (Win32Exception ex)
            {
                if (ex.NativeErrorCode == 1326)
                {
                    throw new InvalidCredentialException("The given username/password combination was invalid.", ex);
                }
                else throw;
            }

            return result;
        }


        #region IDisposable Members

        /// <summary>
        /// Reverts to the original windows identity and releases the resources occupied by this instance
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases all managed and unmanaged resources held by this instance.
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            CredentialEntry entry;

            if (!disposing) return;

            entry = Interlocked.Exchange<CredentialEntry>(ref _CredentialEntry, null);
            if (entry != null) entry.Dispose();

            if (Impersonated)
                RevertToSelf();
        }

        private void RevertToSelf()
        {
            WindowsImpersonationContext context;

            context = Interlocked.Exchange<WindowsImpersonationContext>(ref _Scope, null);
            if (context != null)
            {
                context.Undo();
                context.Dispose();
            }
        }


        #endregion 
    }
}
